/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "lottie_bridge.h"
#include "boundscheck/securec.h"
#include "controller/lottie_handler.h"
#include "node/lottie_manager.h"
#include "task_pool/task_pool.h"
#include <cstdlib>

using namespace LottieTurbo;

enum LottieErrorCode {
    OK,
    NULL_POINTER,
    ILLEGAL_ARGUMENT,
    ADDNODE_FAIL,
    REPEAT_ANIMATOR
};

static napi_env g_bridgeEnv = nullptr;
static std::shared_ptr<NativeResourceManager> GetArkNativeResourceManager()
{
    NativeResourceManager *native = nullptr;
    napi_value global;
    NapiHelper help(g_bridgeEnv);
    napi_get_global(g_bridgeEnv, &global);
    napi_value getContext = help.GetObjectProperty(global, "getContext");
    napi_value context = help.Call(global, getContext, 0, nullptr);
    napi_value jsResMgr = help.GetObjectProperty(context, "resourceManager");
    native = OH_ResourceManager_InitNativeResourceManager(g_bridgeEnv, jsResMgr);
    return std::shared_ptr<NativeResourceManager>(native, [](NativeResourceManager *native) {
        OH_ResourceManager_ReleaseNativeResourceManager(native);
        native = nullptr;
    });
}

static int InitListener(std::shared_ptr<CLottieListener> &listener)
{
    listener->enterFrame = nullptr;
    listener->destroy = nullptr;
    listener->segmentStart = nullptr;
    listener->loopComplete = nullptr;
    listener->loadedImages = nullptr;
    listener->error = nullptr;
    listener->drawFrame = nullptr;
    listener->dOMLoaded = nullptr;
    listener->dataReady = nullptr;
    listener->dataFailed = nullptr;
    listener->configReady = nullptr;
    listener->complete = nullptr;
    return OK;
}

static void SetConfig(std::shared_ptr<LottieConfig> &config, CLottieConfig *lottieConfig)
{
    std::string aData = (lottieConfig->animationData == nullptr) ? ("") : (lottieConfig->animationData);
    std::string id = lottieConfig->lottleId;
    std::string mode = lottieConfig->contentMode;
    std::variant<bool, int> loopConfig;
    if (lottieConfig->loopCount > 0) {
        loopConfig.emplace<int>(lottieConfig->loopCount);
    } else {
        loopConfig.emplace<bool>(lottieConfig->loop);
    }
    std::string pathConfig = lottieConfig->path;
    AnimationSegment segment{lottieConfig->initialSegment.initFrame, lottieConfig->initialSegment.endFrame};
    config->SetUseCache(lottieConfig->useCache);
    config->SetAnimationData(aData);
    config->SetAutoplay(lottieConfig->autoplay);
    config->SetContentMode(mode);
    config->SetFrameRate(lottieConfig->frameRate);
    config->SetLottieId(id);
    config->SetLoop(loopConfig);
    config->SetPath(pathConfig);
    config->SetInitialSegment(segment);
    config->SetResManager(GetArkNativeResourceManager());
}

LottieContext::LottieContext(napi_value contentView, CLottieConfig *lottieConfig)
{
    if (g_bridgeEnv == nullptr) {
        DRAWING_LOGE("create LottieContext faied, first to InitLottieComponentEnvironment");
        return;
    }
    if (lottieConfig == nullptr) {
        return;
    }
    NapiHelper help(g_bridgeEnv);
    ref_ = help.CreateReference(contentView);
    std::string id = lottieConfig->lottleId;
    std::shared_ptr<LottieConfig> config = std::make_shared<LottieConfig>();
    SetConfig(config, lottieConfig);
    lottieNode_ = std::make_shared<LottieXcomponentNode>(id, config->GetRadius(), config->GetbgColor());
    lottieHandle_ = std::make_shared<LottieHandler>(id, std::move(config));
    lottieHandle_->clottieListener_ = std::make_shared<CLottieListener>();
    auto listening_ = lottieHandle_->clottieListener_;
    listening_ = std::make_shared<CLottieListener>();
    InitListener(listening_);
    napi_value workName;
    napi_create_string_utf8(g_bridgeEnv, "LottieListener", NAPI_AUTO_LENGTH, &workName);
    napi_create_threadsafe_function(
        g_bridgeEnv, nullptr, nullptr, workName, 0, 1, nullptr, nullptr, nullptr,
        [](napi_env env, napi_value cb, void *context, void *data) {
            auto trigger = (LottieHandler::TriggerData *)data;
            if (trigger != nullptr) {
                trigger->func();
                delete trigger;
                trigger = nullptr;
            }
        },
        &lottieHandle_->safeFunc_);
}

LottieContext::~LottieContext()
{
    NapiHelper help(g_bridgeEnv);
    help.DeleteReference(ref_);
}

std::shared_ptr<LottieHandler> LottieContext::GetLottieHandle()
{
    return lottieHandle_;
}

std::shared_ptr<LottieXcomponentNode> LottieContext::GetXcomponentNode()
{
    return lottieNode_;
}

ArkUI_NodeContentHandle LottieContext::GetNodeContentHandle()
{
    NapiHelper help(g_bridgeEnv);
    napi_value value = help.GetReferenceValue(ref_);
    return help.GetNodeContentHandle(value);
}

void *LottieCreateContext(napi_value contentView, CLottieConfig *config)
{
    LottieContext *context = new LottieContext(contentView, config);
    if (context) {
        auto listening = context->GetLottieHandle()->clottieListener_;
        if (listening && listening->configReady) {
            listening->configReady();
        }
    }
    return context;
}

int LottieDestroyContext(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    LottieContext *ccontext = reinterpret_cast<LottieContext *>(context);
    delete ccontext;
    ccontext = nullptr;
    return LottieErrorCode::OK;
}

int LottieCreate(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    bool repeat = LottieManager::GetInstance().RepeatLottieId(ccontext->GetLottieHandle()->GetLottieId());
    if (repeat) {
        return REPEAT_ANIMATOR;
    }
    std::string id = ccontext->GetLottieHandle()->GetLottieId();
    ArkUI_NodeContentHandle node = ccontext->GetNodeContentHandle();
    std::shared_ptr<LottieXcomponentNode> xcomponent = ccontext->GetXcomponentNode();
    std::shared_ptr<LottieHandler> handle = ccontext->GetLottieHandle();
    bool ret = LottieManager::GetInstance().AddLottieNode(id, node, xcomponent, handle);
    if (ret == false) {
        return ADDNODE_FAIL;
    }

    auto task = [](std::shared_ptr<LottieHandler> handler, std::shared_ptr<LottieXcomponentNode> node) {
        if (handler && node) {
            node->SetHandler(handler);
            node->SetAnimator();
            HITRACE("Lottie::InitHandleData", handler->InitHandleData());
            HITRACE("Lottie::NodeInit", node->Init());
        }
    };
    TaskPool::GetInstance().PushTask(ffrt_queue_priority_high, task, handle, xcomponent);
    return OK;
}

int LottieAddListeningFunctionByName(void *context, char *funcName, CLottieListenerFunction listening)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (ccontext->GetLottieHandle() == nullptr || ccontext->GetLottieHandle()->clottieListener_ == nullptr) {
        return NULL_POINTER;
    }
    if (std::strcmp(funcName, "enterFrame") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->enterFrame = listening;
    }
    if (std::strcmp(funcName, "loopComplete") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->loopComplete = listening;
    }
    if (std::strcmp(funcName, "complete") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->complete = listening;
    }
    if (std::strcmp(funcName, "segmentStart") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->segmentStart = listening;
    }
    if (std::strcmp(funcName, "destroy") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->destroy = listening;
    }
    if (std::strcmp(funcName, "config_ready") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->configReady = listening;
    }
    if (std::strcmp(funcName, "data_ready") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dataReady = listening;
    }
    if (std::strcmp(funcName, "DOMLoaded") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dOMLoaded = listening;
    }
    if (std::strcmp(funcName, "error") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->error = listening;
    }
    if (std::strcmp(funcName, "data_failed") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dataFailed = listening;
    }
    if (std::strcmp(funcName, "loaded_images") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->loadedImages = listening;
    }
    return OK;
}

int LottieRemoveListeningFunctionByName(void *context, char *funcName)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (ccontext->GetLottieHandle() == nullptr || ccontext->GetLottieHandle()->clottieListener_ == nullptr) {
        return NULL_POINTER;
    }
    if (std::strcmp(funcName, "enterFrame") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->enterFrame = nullptr;
    }
    if (std::strcmp(funcName, "loopComplete") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->loopComplete = nullptr;
    }
    if (std::strcmp(funcName, "complete") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->complete = nullptr;
    }
    if (std::strcmp(funcName, "segmentStart") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->segmentStart = nullptr;
    }
    if (std::strcmp(funcName, "destroy") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->destroy = nullptr;
    }
    if (std::strcmp(funcName, "config_ready") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->configReady = nullptr;
    }
    if (std::strcmp(funcName, "data_ready") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dataReady = nullptr;
    }
    if (std::strcmp(funcName, "DOMLoaded") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dOMLoaded = nullptr;
    }
    if (std::strcmp(funcName, "error") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->error = nullptr;
    }
    if (std::strcmp(funcName, "data_failed") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->dataFailed = nullptr;
    }
    if (std::strcmp(funcName, "loaded_images") == 0) {
        ccontext->GetLottieHandle()->clottieListener_->loadedImages = nullptr;
    }
    return OK;
}

int LottieRemoveAllListeningFunctionByName(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (ccontext->GetLottieHandle() == nullptr || ccontext->GetLottieHandle()->clottieListener_ == nullptr) {
        return NULL_POINTER;
    }
    ccontext->GetLottieHandle()->clottieListener_->enterFrame = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->loopComplete = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->complete = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->segmentStart = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->destroy = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->configReady = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->dataReady = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->dOMLoaded = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->error = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->dataFailed = nullptr;
    ccontext->GetLottieHandle()->clottieListener_->loadedImages = nullptr;
    return OK;
}

int LottiePlay(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->Play();
    return LottieErrorCode::OK;
}

int LottiePause(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->Pause();
    return LottieErrorCode::OK;
}

int LottieStop(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->Stop();
    return LottieErrorCode::OK;
}

int LottieTogglePause(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->TogglePause();
    return LottieErrorCode::OK;
}

static void HandleDestroyLottieNode(std::string id)
{
    auto func = [](std::string id) { LottieManager::GetInstance().DisposeLottieNode(id); };
    auto trigger = new LottieHandler::TriggerData;
    trigger->func = std::bind(func, id);
    auto handler = LottieManager::GetInstance().GetHandleById(id);
    if (handler == nullptr) {
        return;
    }
    napi_call_threadsafe_function(handler->safeFunc_, trigger, napi_tsfn_blocking);
}

int LottieDestroy(void *context)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->isDestroy_ = true;
    std::string id = ccontext->GetLottieHandle()->GetLottieId();
    // 这里不能直接释放，需要加入任务队列，保证任务的时序问题，不然会出现节点被销毁但是还在渲染，导致cppcrash，先跑完/清空队列里面的渲染任务
    TaskPoolLocal::GetInstance().PushTask(HandleDestroyLottieNode, id);

    return OK;
}

int LottieGoToAndStop(void *context, float value, bool isFrame)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    std::optional<bool> is(isFrame);
    ccontext->GetLottieHandle()->GoToAndStop(value, is);
    return LottieErrorCode::OK;
}

int LottieGoToAndPlay(void *context, float value, bool isFrame)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    std::optional<bool> is(isFrame);
    ccontext->GetLottieHandle()->GoToAndPlay(value, is);
    return LottieErrorCode::OK;
}

int LottieSetSegment(void *context, float init, float end)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    AnimationSegment segment{init, end};
    ccontext->GetLottieHandle()->SetSegments(segment);
    return LottieErrorCode::OK;
}

int LottiePlaySegments(void *context, CAnimationSegment *segments, size_t number, bool forceFlag)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (segments == nullptr || number <= 0) {
        return ILLEGAL_ARGUMENT;
    }
    std::vector<AnimationSegment> vec;
    for (int i = 0; i < number; i++) {
        vec.push_back({segments[i].initFrame, segments[i].endFrame});
    }
    ccontext->GetLottieHandle()->SetSegments(std::move(vec));
    ccontext->GetLottieHandle()->Play();
    return LottieErrorCode::OK;
}

int LottieResetSegments(void *context, bool forceFlag)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->ResetSegments(forceFlag);
    return LottieErrorCode::OK;
}

int LottieSetSpeed(void *context, double speed)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->SetSpeed(speed);
    return LottieErrorCode::OK;
}

int LottieSetDirection(void *context, int direction)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->SetDirection(direction);
    return LottieErrorCode::OK;
}

int LottieSetSubframe(void *context, bool useSubFrames)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->SetIsSubframeEnabled(useSubFrames);
    return LottieErrorCode::OK;
}

double LottieGetDuration(void *context, bool inFrames)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    auto handle = ccontext->GetLottieHandle();
    auto totalFrames = handle->GetTotalFrames();
    auto fileTotalFrames = handle->GetLottieFileTotalFrameRate();
    if (fileTotalFrames == 0) {
        return 0;
    }
    double duration = inFrames ? totalFrames : (totalFrames / fileTotalFrames);
    return duration;
}

int LottieChangeColor(void *context, char *layger, CColorSegment *colorSegment)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    std::vector<float> start;
    std::vector<float> end;
    for (int i = 0; i < colorSegment->startColorNumber; i++) {
        start.push_back(colorSegment->startColor[i]);
    }
    for (int i = 0; i < colorSegment->endColorNumber; i++) {
        end.push_back(colorSegment->endColor[i]);
    }
    ccontext->GetLottieHandle()->ChangeColor(layger, start, end);
    return LottieErrorCode::OK;
}

int LottieSetContentMode(void *context, char *contentMode)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    std::string mode = contentMode;
    ccontext->GetLottieHandle()->GetLottieConfig()->SetContentMode(mode);
    if (ccontext->GetLottieHandle()->updateContentMode) {
        ccontext->GetLottieHandle()->updateContentMode();
    }
    if (ccontext->GetLottieHandle()->renderCurrentFrame) {
        ccontext->GetLottieHandle()->renderCurrentFrame();
    }
    return LottieErrorCode::OK;
}

int LottieSetFrameRate(void *context, int frameRate)
{
    if (context == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    ccontext->GetLottieHandle()->SetFrameRate(frameRate);
    return LottieErrorCode::OK;
}

int LottieReload(void *context, void *reContext)
{
    if (context == nullptr || reContext == nullptr) {
        return NULL_POINTER;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (ccontext->GetLottieHandle() == nullptr || ccontext->GetLottieHandle()->GetLottieConfig() == nullptr) {
        return NULL_POINTER;
    }
    auto reCcontext = reinterpret_cast<LottieContext *>(reContext);
    if (reCcontext->GetLottieHandle() == nullptr || reCcontext->GetLottieHandle()->GetLottieConfig() == nullptr) {
        return NULL_POINTER;
    }

    std::string path = reCcontext->GetLottieHandle()->GetLottieConfig()->GetPath();
    double frameRate = reCcontext->GetLottieHandle()->GetLottieConfig()->GetFrameRate();
    std::string contentMode = reCcontext->GetLottieHandle()->GetLottieConfig()->GetContentMode();
    std::vector<AnimationSegment> segments = reCcontext->GetLottieHandle()->GetLottieConfig()->GetSegments();
    std::string animationData = reCcontext->GetLottieHandle()->GetLottieConfig()->GetAnimationData();
    bool autoPlay = reCcontext->GetLottieHandle()->GetLottieConfig()->GetAutoplay();
    AnimationSegment initialSegment = reCcontext->GetLottieHandle()->GetLottieConfig()->GetInitialSegment();
    std::variant<bool, int> loop = reCcontext->GetLottieHandle()->GetLottieConfig()->GetLoop();
    std::string lottieId = reCcontext->GetLottieHandle()->GetLottieConfig()->GetLottieId();
    bool useCache = reCcontext->GetLottieHandle()->GetLottieConfig()->GetUseCache();

    ccontext->GetLottieHandle()->GetLottieConfig()->SetPath(path);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetFrameRate(frameRate);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetContentMode(contentMode);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetSegments(segments);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetAnimationData(animationData);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetAutoplay(autoPlay);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetInitialSegment(initialSegment);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetLoop(loop);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetLottieId(lottieId);
    ccontext->GetLottieHandle()->GetLottieConfig()->SetUseCache(useCache);

    ccontext->GetLottieHandle()->Reload();
    return LottieErrorCode::OK;
}

int InitLottieComponentEnvironment(napi_env env)
{
    g_bridgeEnv = env;
    return OK;
}

CLottieConfig *CreateCLottieConfig()
{
    CLottieConfig *configLottie = (CLottieConfig*) malloc(sizeof(CLottieConfig));
    if (configLottie == nullptr) {
        return nullptr;
    }
    const int defaultFrameRate = 60;
    static std::atomic<int> index = 0;
    configLottie->autoplay = true;
    memset_s(configLottie->path, sizeof(configLottie->path), 0, sizeof(configLottie->path));
    memcpy_s(configLottie->contentMode, sizeof(configLottie->contentMode), "Contain\0", strlen("Contain\0"));
    (void)sprintf_s(configLottie->lottleId, sizeof(configLottie->lottleId), "lottieid-%d", index++);
    configLottie->bgColor = 0.0;
    configLottie->frameRate = defaultFrameRate;
    configLottie->initialSegment = {0.0, 0.0};
    configLottie->loop = true;
    configLottie->loopCount = 0;
    configLottie->radius = 0.0;
    configLottie->useCache = true;
    configLottie->useImageCache = false;
    configLottie->animationData = nullptr;
    return configLottie;
}

void DestroyCLottieConfig(CLottieConfig *config)
{
    if (config != nullptr) {
        free(config);
        config = nullptr;
    }
}

const char *LottieGetId(void *context)
{
    if (context == nullptr) {
        return nullptr;
    }
    auto ccontext = reinterpret_cast<LottieContext *>(context);
    if (ccontext->GetLottieHandle() == nullptr) {
        return nullptr;
    }
    return ccontext->GetLottieHandle()->GetLottieId().c_str();
}